#include <uart.h>
#include <arch/io.h>
#include <arch/cpu/soc/s3c2440.h>
#include <irq.h>
#include <arch/cpu/soc/irqs.h>
#include <clk.h>
#include <errno.h>
#include <config.h>
#include <device.h>

//debug
#include <string.h>

#define S3C2440_UART_BUFSIZE	(512) 

struct s3c2440_uart_priv {
	unsigned int index;
	struct clk *pclk;
	struct clk *clk_gpio;
	struct clk *clk_uart;
	unsigned int baudrate;
	unsigned int rx_irq;
	unsigned int tx_irq;
};

static struct s3c2440_uart_priv s3c2440_uart0_priv = {
	.index = 0,
};
static struct s3c2440_uart_priv s3c2440_uart1_priv = {
	.index = 1,
};

static struct s3c2440_uart_priv s3c2440_uart2_priv = {
	.index = 2,
};

static struct irqaction s3c2440_uart_rx_irqaction;
static struct irqaction s3c2440_uart_tx_irqaction;

static struct char_with_flags s3c2440_uart_rxbuf[S3C2440_UART_BUFSIZE];
static struct char_with_flags s3c2440_uart_txbuf[S3C2440_UART_BUFSIZE];
static unsigned int s3c2440_uart_rxbuf_head;
static unsigned int s3c2440_uart_rxbuf_tail;
static unsigned int s3c2440_uart_txbuf_head;
static unsigned int s3c2440_uart_txbuf_tail;

static int s3c2440_uart_is_tx_full(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	
	return readl(&uart->ufstat) & (1 << 14);
}

static void s3c2440_uart_stop_tx(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp  = readl(&uart->ucon);
	tmp &= ~(3 << 2);
	writel(tmp, &uart->ucon);

	disable_irq(IRQ_TXD0);
}

static void s3c2440_uart_start_tx(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp  = readl(&uart->ucon);
	tmp &= ~(3 << 2);
	tmp |= (1 << 2);
	writel(tmp, &uart->ucon);

	enable_irq(IRQ_TXD0);
}

static int s3c2440_uart_is_tx_empty(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	
	return readl(&uart->utrstat) & (1 << 2);
}
    
static int s3c2440_uart_is_rx_empty(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	
	return !(readl(&uart->ufstat) & 0x3f);
}

static void s3c2440_uart_start_rx(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp  = readl(&uart->ucon);
	tmp &= ~(3 << 0);
	tmp |= (1 << 0);
	writel(tmp, &uart->ucon);

	enable_irq(IRQ_RXD0);
}

static void s3c2440_uart_stop_rx(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp  = readl(&uart->ucon);
	tmp &= ~(3 << 0);
	writel(tmp, &uart->ucon);

	disable_irq(IRQ_RXD0);
}
    
static irqreturn_t s3c2440_uart_rx_irq(unsigned int irq, void *priv)
{
	struct uart_device *udev = priv;
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;
	unsigned char fg;
	
	while (readl(&uart->ufstat) & 0x3f) {
		tmp = (s3c2440_uart_rxbuf_tail + 1) & (S3C2440_UART_BUFSIZE - 1);
		if (tmp != s3c2440_uart_rxbuf_head) {
			s3c2440_uart_rxbuf_tail = tmp;
			fg = 0;
			tmp = readl(&uart->uerstat);			
			if (tmp & (1 << 3)) 
				fg = fg | UART_CHAR_BREAK;
			if (tmp & 0x7)
				fg = fg | UART_CHAR_ERROR;
			s3c2440_uart_rxbuf[s3c2440_uart_rxbuf_tail].fg = fg;
			s3c2440_uart_rxbuf[s3c2440_uart_rxbuf_tail].ch = readb(&uart->urxh);			
		} else {
			break;
		}
	}
	
	return IRQ_HANDLED;
}

static irqreturn_t s3c2440_uart_tx_irq(unsigned int irq, void *priv)
{
	struct uart_device *udev = priv;
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	
	while (!(readl(&uart->ufstat) & (1 << 14))) {		
		if (s3c2440_uart_txbuf_head != s3c2440_uart_txbuf_tail) {
			writeb(s3c2440_uart_txbuf[s3c2440_uart_txbuf_head].ch, &uart->utxh);
			s3c2440_uart_txbuf_head = (s3c2440_uart_txbuf_head + 1) & (S3C2440_UART_BUFSIZE - 1);		
		}
	}

	return IRQ_HANDLED;
}

static int s3c2440_uart_startup(struct uart_device *udev)
{
	struct s3c2440_uart *uart;
	struct s3c2440_gpio *gpio = s3c2440_get_base_gpio();
	struct s3c2440_uart_priv *priv = udev->priv;
	struct clk *clk;
	int ret;
	uint32_t tmp;

	switch (priv->index) {
		case 0:
			udev->membase = s3c2440_get_base_uart(0);
			break;
		case 1:
			udev->membase = s3c2440_get_base_uart(1);
			break;
		case 2:
			udev->membase = s3c2440_get_base_uart(2);
			break;
		default:
			return -ENODEV;
	}
	uart = (struct s3c2440_uart *)udev->membase;

	clk = clk_get("pclk");
	if (strcmp("pclk", clk->name) == 0) 
	// debug
	{
		struct s3c2440_gpio *gpio = s3c2440_get_base_gpio(); 
		uint32_t tmp;

		tmp  = readl(&gpio->gpfdat);
		tmp &= ~0x10;
		writel(tmp, &gpio->gpfdat);
	}
	
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		return ret;
	priv->pclk = clk;


	clk = clk_get("gpio");
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		goto err_clk_gpio;
	priv->clk_gpio = clk;

	clk = NULL;
	switch (priv->index) {
		case 0:
			clk = clk_get("uart0");
			break;
		case 1:
			clk = clk_get("uart1");
			break;
		case 2:
			clk = clk_get("uart2");
			break;
	}
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		goto err_clk_uart;
	priv->clk_uart = clk;		

	tmp  = readl(&gpio->gphcon) & ~(0xf << (4 + priv->index * 4));
	tmp |= (0xa << (4 + priv->index * 4));
	writel(tmp, &gpio->gphcon);
	tmp  = readl(&gpio->gphup) & ~(3 << (2 + priv->index * 2));
	writel(tmp, &gpio->gphup);
	
	writel(0x07, &uart->ufcon);		// FIFO enable, reset Tx/Rx FIFO
	if (priv->index != 2) {
		writel(0, &uart->umcon);	// no auto flow control
	}
	writel(0x3, &uart->ulcon);		// no parity, 1 stop, 8 bit
	writel(0x5, &uart->ucon);		// interrupt/polling mode, pulse mode, PCLK for baudrate

	tmp = clk_get_rate(priv->pclk);
	if (!tmp) {
		ret = -ENODEV;
		goto err_set_baud;
	}
	tmp = tmp / (16 * CONFIG_DEFAULT_BAUDRATE) - 1;
	if (tmp >> 16) {
		ret = -EINVAL;
		goto err_set_baud;
	}
	writel(tmp, &uart->ubrdiv);
	priv->baudrate = CONFIG_DEFAULT_BAUDRATE;		
	
	switch (priv->index) {
		case 0:
			priv->rx_irq = IRQ_RXD0;
			priv->tx_irq = IRQ_TXD0;
			break;
		case 1:
			priv->rx_irq = IRQ_RXD1;
			priv->tx_irq = IRQ_TXD1;
			break;
		case 2:
			priv->rx_irq = IRQ_RXD2;
			priv->tx_irq = IRQ_TXD2;
			break;
	}
	ret = request_irq(priv->rx_irq, &s3c2440_uart_rx_irqaction, s3c2440_uart_rx_irq, IRQF_DISABLE, udev);
	if (IS_ERR(ret)) 
		goto err_rx_irq;
		
	ret = request_irq(priv->tx_irq, &s3c2440_uart_tx_irqaction, s3c2440_uart_tx_irq, IRQF_DISABLE, udev);
	if (IS_ERR(ret))
		goto err_tx_irq;
	
	return 0;

err_tx_irq:
	free_irq(priv->rx_irq, udev);
err_rx_irq:
err_set_baud:
	clk_disable(priv->clk_uart);
err_clk_uart:
	clk_disable(priv->clk_gpio);
err_clk_gpio:
	clk_disable(priv->pclk);

	return ret;
}

static void s3c2440_uart_shutdown(struct uart_device *udev)
{
	struct s3c2440_uart_priv *priv = udev->priv;

	clk_disable(priv->clk_uart);
	clk_disable(priv->clk_gpio);
	clk_disable(priv->pclk);
	free_irq(priv->rx_irq, &udev);
	free_irq(priv->tx_irq, &udev);
}

static int s3c2440_uart_get_data_bits(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp = readl(&uart->ulcon);
	switch (tmp & 0x3) {
		case 0:
			return 5;
		case 1:
			return 6;
		case 2:
			return 7;
		case 3:
			return 8;
	}	
	return 0;
}

static int s3c2440_uart_set_data_bits(struct uart_device *udev, uint8_t bits)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	if ((bits < 5) || (bits > 8))
		return -EINVAL;
		
	tmp  = readl(&uart->ulcon);
	tmp &= ~3;
	switch (bits) {
		case 5:
			break;
		case 6:
			tmp |= 1;
			break;
		case 7:
			tmp |= 2;
			break;
		case 8:
			tmp |= 3;
			break;
	}
	writel(tmp, &uart->ulcon);
	return 0;
}

static int s3c2440_uart_get_stop_bits(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp = readl(&uart->ulcon);
	if (tmp & (1 << 2)) {
		return 2;
	} else {
		return 1;
	}	
	return 0;
}

static int s3c2440_uart_set_stop_bits(struct uart_device *udev, uint8_t bits)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	if ((bits < 1) || (bits > 2))
		return -EINVAL;
		
	tmp  = readl(&uart->ulcon);
	if (bits == 1) 
		tmp &= ~(1 << 2);
	else
		tmp |= (1 << 2);
	writel(tmp, &uart->ulcon);
	return 0;
}

static int s3c2440_uart_get_baudrate(struct uart_device *udev)
{
	struct s3c2440_uart_priv *priv = udev->priv;

	return priv->baudrate;
}

static int s3c2440_uart_set_baudrate(struct uart_device *udev, uint32_t baud)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	struct s3c2440_uart_priv *priv = udev->priv;
	uint32_t tmp;
	
	if (baud == 0)
		return -EINVAL;
		
	tmp = clk_get_rate(priv->pclk);
	if (!tmp) 
		return -EFAULT;
		
	tmp = tmp / (16 * baud) - 1;
	if (tmp >> 16) 
		return -EINVAL;
		
	writel(tmp, &uart->ubrdiv);
	priv->baudrate = baud;
	return 0;
}

static int s3c2440_uart_get_parity_bit(struct uart_device *udev)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp = readl(&uart->ulcon);
	switch ((tmp & (7 << 3)) >> 3) {
		case 0:
			return UART_PARITY_NO;
		case 4:
			return UART_PARITY_ODD;
		case 5:
			return UART_PARITY_EVEN;
		default:
			return -EFAULT;
	}	
	return 0;
}

static int s3c2440_uart_set_parity_bit(struct uart_device *udev, uint8_t parity)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	tmp  = readl(&uart->ulcon);
	tmp &= ~(7 << 3);
	switch (parity) {
		case UART_PARITY_NO:
			break;
		case UART_PARITY_ODD:
			tmp |= 4;
			break;
		case UART_PARITY_EVEN:
			tmp |= 5;
			break;
		default:
			return -EINVAL;
	}
	writel(tmp, &uart->ulcon);
	return 0;
}
    
static int s3c2440_uart_send_char(struct uart_device *udev, unsigned char ch, unsigned char fg)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;

	if (fg) 
		return -ENOTSUPP;
		
	if (s3c2440_uart_is_tx_full(udev)) 
		return -EIO;
		
	// debug
	{
		struct s3c2440_gpio *gpio = s3c2440_get_base_gpio(); 
		uint32_t tmp;

		tmp  = readl(&gpio->gpfdat);
		tmp &= ~0x20;
		writel(tmp, &gpio->gpfdat);
	}

	writeb(ch, &uart->utxh);
	return 0;
}

static int s3c2440_uart_recv_char(struct uart_device *udev, unsigned char *cp, unsigned char *fp)
{
	struct s3c2440_uart *uart = (struct s3c2440_uart *)udev->membase;
	uint32_t tmp;

	if (s3c2440_uart_is_rx_empty(udev)) 
		return -EIO;
		
	*fp = 0;
	tmp = readl(&uart->uerstat);			
	if (tmp & (1 << 3)) 
		*fp = *fp | UART_CHAR_BREAK;
	if (tmp & 0x7)
		*fp = *fp | UART_CHAR_ERROR;
	*cp = readb(&uart->urxh);			
	return 0;
}
    
static struct uart_operations s3c2440_uart_ops = {
	.is_tx_full		= s3c2440_uart_is_tx_full,
	.stop_tx		= s3c2440_uart_stop_tx,
	.start_tx		= s3c2440_uart_start_tx,
	.is_tx_empty	= s3c2440_uart_is_tx_empty,
	.is_rx_empty	= s3c2440_uart_is_rx_empty,
	.start_rx		= s3c2440_uart_start_rx,
	.stop_rx		= s3c2440_uart_stop_rx,
	.startup		= s3c2440_uart_startup,
	.shutdown		= s3c2440_uart_shutdown,
	.get_data_bits	= s3c2440_uart_get_data_bits,
	.set_data_bits	= s3c2440_uart_set_data_bits,
	.get_stop_bits	= s3c2440_uart_get_stop_bits,
	.set_stop_bits	= s3c2440_uart_set_stop_bits,
	.get_baudrate	= s3c2440_uart_get_baudrate,
	.set_baudrate	= s3c2440_uart_set_baudrate,
	.get_parity_bit	= s3c2440_uart_get_parity_bit,
	.set_parity_bit	= s3c2440_uart_set_parity_bit,
	.send_char		= s3c2440_uart_send_char,
	.recv_char		= s3c2440_uart_recv_char,
};

static struct uart_device s3c2440_uart0_uart_device;

DEVICE(s3c2440_uart0_device, "uart0", MKDEV(MAJOR_UART, 0), 
	DEV_FLAGS_CAN_READ | DEV_FLAGS_CAN_WRITE | DEV_FLAGS_CAN_NONBLOCK, 
	&s3c2440_uart0_uart_device.tdev);

static struct uart_device s3c2440_uart0_uart_device = {
	.tdev = {
		.dev = &s3c2440_uart0_device,
	},
	.priv = &s3c2440_uart0_priv,
};

static struct uart_device s3c2440_uart1_uart_device;

DEVICE(s3c2440_uart1_device, "uart1", MKDEV(MAJOR_UART, 1), 
	DEV_FLAGS_CAN_READ | DEV_FLAGS_CAN_WRITE | DEV_FLAGS_CAN_NONBLOCK, 
	&s3c2440_uart1_uart_device.tdev);

static struct uart_device s3c2440_uart1_uart_device = {
	.tdev = {
		.dev = &s3c2440_uart1_device,
	},
	.priv = &s3c2440_uart1_priv,
};

static struct uart_device s3c2440_uart2_uart_device;

DEVICE(s3c2440_uart2_device, "uart2", MKDEV(MAJOR_UART, 2), 
	DEV_FLAGS_CAN_READ | DEV_FLAGS_CAN_WRITE | DEV_FLAGS_CAN_NONBLOCK, 
	&s3c2440_uart2_uart_device.tdev);

static struct uart_device s3c2440_uart2_uart_device = {
	.tdev = {
		.dev = &s3c2440_uart2_device,
	},
	.priv = &s3c2440_uart2_priv,
};

static struct uart_driver s3c2440_uart_uart_driver;

DRIVER(s3c2440_uart_driver, NULL, MKDEV(MAJOR_UART, 0), MKDEV(MAJOR_UART, 2), 
	&tty_dev_ops, &s3c2440_uart_uart_driver.tdrv);

static struct uart_driver s3c2440_uart_uart_driver = {
	.tdrv = {
		.drv = &s3c2440_uart_driver,
		.ops = &uart_dev_ops,
	},
	.ops = &s3c2440_uart_ops,
};

